import copy

from django.contrib import admin
from django.utils import importlib
from django.contrib.admin.sites import site as admin_site
from django.contrib.admin.views.main import ChangeList
from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response

class ForeignkeyLink:
    def foreignkey(self):
        field = getattr(self, foreignkey.field)
        return '<a href="%s">%s</a>' % (reverse('admin:%s_%s_change' % (field._meta.app_label, field._meta.module_name), args=[field.pk]), field)

class ChangeListPlus(ChangeList):
    def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin, list_resolve_foreign_keys):
        list_display_rewritten = ()
        
        def foreignkey_link(self, field_name):
            field = getattr(self, field_name)
            return '<a href="%s">%s</a>' % (reverse('admin:%s_%s_change' % (field._meta.app_label, field._meta.module_name), args=[field.pk]), field)
        
        setattr(model, '_foreignkey_link', foreignkey_link)
        
        def generate_foreignkey_link(field):
            return lambda self: self._foreignkey_link(field)
            
        for link in list_display:
            if link in list_resolve_foreign_keys:
                t = generate_foreignkey_link(link)
                t.allow_tags = True
                t.short_description = link
                t.field = link
                setattr(model, '_foreignkey_%s_' % link, t)
                list_display_rewritten += ('_foreignkey_%s_' % link, )
            else:
                list_display_rewritten += (link, )
        super(ChangeListPlus, self).__init__(request, model, list_display_rewritten, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin)

    def url_for_result(self, result):
        return reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.module_name), args=[quote(getattr(result, self.pk_attname))])

class ModelAdminPlus(admin.ModelAdmin):
    inline_lists = ()
    list_resolve_foreign_keys = []
    change_form_template = 'adminplus1/change_form.html'
    
    def generate_changelist_for_inline(self, request, admin_model, model, obj, field_to_model=[], title='', field_to_value=[]):
        for field in field_to_model:
            request.GET[field[0]] = getattr(obj, field[1])
        
        for field in field_to_value:
            request.GET[field[0]] = field[1]
        
        list_resolve_foreign_keys = []
        if hasattr(admin_model, 'list_resolve_foreign_keys'):
            list_resolve_foreign_keys = admin_model.list_resolve_foreign_keys
        
        cl = ChangeListPlus(
            request,
            model,
            admin_model.list_display,
            admin_model.list_display_links,
            admin_model.list_filter,
            admin_model.date_hierarchy,
            admin_model.search_fields,
            admin_model.list_select_related,
            admin_model.list_per_page,
            admin_model.list_editable,
            admin_model,
            list_resolve_foreign_keys,
        )
        cl.formset = None
        cl.inline_title = title
        
        return cl
        
    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
        if change:
            cls = []
            for il in self.inline_lists:
                am = getattr(__import__('.'.join(il['admin_model'].split('.')[:-1]), globals(), locals(), [il['admin_model'].split('.')[-1]], -1), il['admin_model'].split('.')[-1])
                m = getattr(__import__('.'.join(il['model'].split('.')[:-1]), globals(), locals(), [il['model'].split('.')[-1]], -1), il['model'].split('.')[-1])
                admin_model = am(m, admin_site)
                new_request = copy.copy(request)
                new_request.GET = {}
                
                extras = {}
                
                for key in ['field_to_model', 'title', 'field_to_value']:
                    if il.has_key(key):
                        extras[key] = il[key]
                
                cls.append(self.generate_changelist_for_inline(
                    new_request,
                    admin_model,
                    m,
                    obj,
                    **extras
                ))
            context.update({'cls': cls})
            
        return super(ModelAdminPlus, self).render_change_form(request, context, add, change, form_url, obj)
    
    def changelist_view(self, request, extra_context=None):
        "The 'change list' admin view for this model."
        
        from django.contrib.admin.views.main import ERROR_FLAG
        from django.contrib.admin.options import IncorrectLookupParameters
        from django import template
        from django.utils.translation import ungettext
        from django.utils.encoding import force_unicode
        from django.http import HttpResponseRedirect
        from django.core.exceptions import PermissionDenied
        
        opts = self.model._meta
        app_label = opts.app_label
        if not self.has_change_permission(request, None):
            raise PermissionDenied

        # Check actions to see if any are available on this changelist
        actions = self.get_actions(request)

        # Remove action checkboxes if there aren't any actions available.
        list_display = list(self.list_display)
        if not actions:
            try:
                list_display.remove('action_checkbox')
            except ValueError:
                pass

        try:
            cl = ChangeListPlus(request, self.model, list_display, self.list_display_links, self.list_filter,
                self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self, self.list_resolve_foreign_keys)
        except IncorrectLookupParameters:
            # Wacky lookup parameters were given, so redirect to the main
            # changelist page, without parameters, and pass an 'invalid=1'
            # parameter via the query string. If wacky parameters were given and
            # the 'invalid=1' parameter was already in the query string, something
            # is screwed up with the database, so display an error page.
            if ERROR_FLAG in request.GET.keys():
                return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')

        # If the request was POSTed, this might be a bulk action or a bulk edit.
        # Try to look up an action first, but if this isn't an action the POST
        # will fall through to the bulk edit check, below.
        if actions and request.method == 'POST':
            response = self.response_action(request, queryset=cl.get_query_set())
            if response:
                return response

        # If we're allowing changelist editing, we need to construct a formset
        # for the changelist given all the fields to be edited. Then we'll
        # use the formset to validate/process POSTed data.
        formset = cl.formset = None

        # Handle POSTed bulk-edit data.
        if request.method == "POST" and self.list_editable:
            FormSet = self.get_changelist_formset(request)
            formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
            if formset.is_valid():
                changecount = 0
                for form in formset.forms:
                    if form.has_changed():
                        obj = self.save_form(request, form, change=True)
                        self.save_model(request, obj, form, change=True)
                        form.save_m2m()
                        change_msg = self.construct_change_message(request, form, None)
                        self.log_change(request, obj, change_msg)
                        changecount += 1

                if changecount:
                    if changecount == 1:
                        name = force_unicode(opts.verbose_name)
                    else:
                        name = force_unicode(opts.verbose_name_plural)
                    msg = ungettext("%(count)s %(name)s was changed successfully.",
                                    "%(count)s %(name)s were changed successfully.",
                                    changecount) % {'count': changecount,
                                                    'name': name,
                                                    'obj': force_unicode(obj)}
                    self.message_user(request, msg)

                return HttpResponseRedirect(request.get_full_path())

        # Handle GET -- construct a formset for display.
        elif self.list_editable:
            FormSet = self.get_changelist_formset(request)
            formset = cl.formset = FormSet(queryset=cl.result_list)

        # Build the list of media to be used by the formset.
        if formset:
            media = self.media + formset.media
        else:
            media = self.media

        # Build the action form and populate it with available actions.
        if actions:
            action_form = self.action_form(auto_id=None)
            action_form.fields['action'].choices = self.get_action_choices(request)
        else:
            action_form = None

        context = {
            'title': cl.title,
            'is_popup': cl.is_popup,
            'cl': cl,
            'media': media,
            'has_add_permission': self.has_add_permission(request),
            'root_path': self.admin_site.root_path,
            'app_label': app_label,
            'action_form': action_form,
            'actions_on_top': self.actions_on_top,
            'actions_on_bottom': self.actions_on_bottom,
        }
        context.update(extra_context or {})
        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
        return render_to_response(self.change_list_template or [
            'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
            'admin/%s/change_list.html' % app_label,
            'admin/change_list.html'
        ], context, context_instance=context_instance) 